package org.amphiprion.gameengine;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import org.amphiprion.gameengine.util.ShaderHelper;
import org.amphiprion.gameengine.util.Texture;
import org.amphiprion.gameengine.util.TextureHelper;
import org.amphiprion.gameengine.util.Vector4;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;

public class Image2D implements IObject2D, IHMIComponent {
	/** How many bytes per float. */
	private final int mBytesPerFloat = 4;

	/** Size of the texture coordinate data in elements. */
	private final int mTextureCoordinateDataSize = 2;

	private Context context;
	private boolean enable = true;

	// Our vertex buffer.
	private FloatBuffer mVerticesBuffer = null;

	// Our UV texture buffer.
	private FloatBuffer mTextureBuffer; // New variable.

	// The bitmap we want to load as a texture.
	protected String uri; // New variable.
	protected Texture texture; // New variable.

	private int nbVertex;
	private IObject2D parent;

	public float rotationDegree; // degree

	// Smooth Colors
	private float[] mColorBuffer = null;
	private boolean visible = true;
	// Translate params.
	public int x = 0;
	public int y = 0;
	private int width;
	private int height;
	public Vector4 maskColor = new Vector4(1, 1, 1, 1);

	public enum Alignment {
		CENTER, LEFT
	}

	public Alignment alignment = Alignment.CENTER;

	private float scale = 1.0f;
	public Object tagObject;

	public Image2D(Context context, String uri) {
		this.context = context;
		this.uri = uri;

		// setVertices(vertices);
		// setTextureCoordinates(textureCoordinates);
	}

	int program = -1;
	private Vector4 tmpMaskColor = new Vector4(1, 1, 1, 1);
	private float[] tmpClip = new float[4];

	@Override
	public void draw(float[] mMVPMatrix, float[] mViewMatrix, float[] mModelMatrix, float[] mProjectionMatrix, ScreenProperty sp) {
		float screenScaleX = sp.screenScaleX;
		float screenScaleY = sp.screenScaleY;
		int screenWidth = (int) sp.realWidth;
		int screenHeight = (int) sp.realHeight;

		// Set flat color
		if (!enable) {
			// gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
			// GL10.GL_MODULATE);
			tmpMaskColor.X = 0.4f * sp.maskColorRed * maskColor.X;
			tmpMaskColor.Y = 0.4f * sp.maskColorGreen * maskColor.Y;
			tmpMaskColor.Z = 0.4f * sp.maskColorBlue * maskColor.Z;
			tmpMaskColor.W = sp.maskColorAlpha * maskColor.W;

			// GL2.Color4 (0.4f * sp.maskColorRed, 0.4f * sp.maskColorGreen, 0.4f * sp.maskColorBlue, 1.0f * sp.maskColorAlpha);
		} else {
			tmpMaskColor.X = sp.maskColorRed * maskColor.X;
			tmpMaskColor.Y = sp.maskColorGreen * maskColor.Y;
			tmpMaskColor.Z = sp.maskColorBlue * maskColor.Z;
			tmpMaskColor.W = sp.maskColorAlpha * maskColor.W;
			// GL2.Color4 (sp.maskColorRed, sp.maskColorGreen, sp.maskColorBlue, sp.maskColorAlpha);
		}
		//
		// GL2.FrontFace (All2.Ccw);
		// // Enable face culling.
		// GL2.Enable (All2.CullFace);
		// // What faces to remove with the face culling.
		// GL2.CullFace (All2.Back);
		// New part...
		if (uri != null && (texture == null || !texture.loaded)) {
			loadGLTexture();
		}

		// ////////////////////////////////////
		// ////////////////////////////////////
		// ////////////////////////////////////

		Matrix.setIdentityM(mModelMatrix, 0);
		if (alignment == Alignment.CENTER) {
			Matrix.translateM(mModelMatrix, 0, x * screenScaleX, -(y - sp.referenceHeight) * screenScaleY, 0);
		} else {
			Matrix.translateM(mModelMatrix, 0, (x + texture.originalWidth * scale / 2) * screenScaleX, -(y - sp.referenceHeight) * screenScaleY, 0);
		}
		Matrix.scaleM(mModelMatrix, 0, scale * screenScaleX, scale * screenScaleY, 1);
		Matrix.rotateM(mModelMatrix, 0, rotationDegree, 0.0f, 0.0f, 1.0f);
		// This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
		// (which currently contains model * view).
		// Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
		// This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
		// (which now contains model * view * projection).
		// Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
		Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mModelMatrix, 0);

		if (program == -1) {
			program = ShaderHelper.getProgramHandle("IMAGE2D");
		}
		GLES20.glUseProgram(program);
		int mMVPMatrixHandle = GLES20.glGetUniformLocation(program, "u_MVPMatrix");
		int mPositionHandle = GLES20.glGetAttribLocation(program, "a_Position");
		int mColorHandle = GLES20.glGetUniformLocation(program, "color");
		int mClippingHandle = GLES20.glGetUniformLocation(program, "clipping");
		int mTextureUniformHandle = GLES20.glGetUniformLocation(program, "texture1");
		int mTextureCoordinateHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");

		// Pass in the combined matrix.
		GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);

		// the vertex coordinates
		mVerticesBuffer.position(0);
		GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVerticesBuffer);

		GLES20.glEnableVertexAttribArray(0);

		// the normal info
		// GL2.VertexAttribPointer(BasicShader.ATTRIB_NORMAL_COORD_INDEX, 3, All2.Float, false, 0, mNormalBuffer);
		// GL2.EnableVertexAttribArray(BasicShader.ATTRIB_NORMAL_COORD_INDEX);

		// GL2.Uniform1(Image2DShader.UNIFORM_COLOR_LOCATION, 0);
		GLES20.glUniform4fv(mColorHandle, 1, tmpMaskColor.getArray(), 0);

		Vector4 clipping = sp.getClipping();
		if (clipping != null) {
			float[] clip = clipping.getArray();
			tmpClip[0] = clip[0] * screenScaleX;
			tmpClip[1] = -(clip[1] + clip[3] - 1 - sp.referenceHeight) * screenScaleY;
			tmpClip[2] = (clip[0] + clip[2] - 1) * screenScaleX;
			tmpClip[3] = -(clip[1] - sp.referenceHeight) * screenScaleY;
			// Log.d("CLIPPING", "x=" + tmpClip[0] + " y=" + tmpClip[1] + " w=" + tmpClip[2] + " h=" + tmpClip[3]);
			GLES20.glUniform4fv(mClippingHandle, 1, tmpClip, 0);
		} else {
			GLES20.glUniform4fv(mClippingHandle, 1, ScreenProperty.NO_CLIPPING.getArray(), 0);
		}

		// Texture info
		// bind textures
		if (texture != null) {// && enableTexture) {
			// TODO rejouter un parameter dans le shader pour dire si y a ou pas une texture

			GLES20.glActiveTexture(GLES20.GL_TEXTURE);
			GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture.textureId);
			GLES20.glUniform1i(mTextureUniformHandle, 0);

			// texture coordinates
			mTextureBuffer.position(0);
			GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mTextureBuffer);

			// GLES20.glEnableVertexAttribArray(2);
		}

		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, nbVertex);
	}

	/**
	 * Set the vertices.
	 * 
	 * @param vertices
	 */
	protected void setVertices(float[] vertices) {
		nbVertex = vertices.length / 3;
		// a float is 4 bytes, therefore we multiply the number if
		// vertices with 4.
		// ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
		// vbb.order(ByteOrder.nativeOrder());
		// mVerticesBuffer = vbb.asFloatBuffer();
		// mVerticesBuffer.put(vertices);
		// mVerticesBuffer.position(0);
		mVerticesBuffer = ByteBuffer.allocateDirect(vertices.length * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
		mVerticesBuffer.put(vertices).position(0);

	}

	/**
	 * Set the texture coordinates.
	 * 
	 * @param textureCoords
	 */
	protected void setTextureCoordinates(float[] textureCoords) { // New
																	// function.
																	// float is 4 bytes, therefore we multiply the number if
																	// vertices with 4.
																	// ByteBuffer byteBuf = ByteBuffer.allocateDirect(textureCoords.length * 4);
		// byteBuf.order(ByteOrder.nativeOrder());
		// mTextureBuffer = byteBuf.asFloatBuffer();
		// mTextureBuffer.put(textureCoords);
		// mTextureBuffer.position(0);
		mTextureBuffer = ByteBuffer.allocateDirect(textureCoords.length * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
		mTextureBuffer.put(textureCoords).position(0);
	}

	/**
	 * Set the colors
	 * 
	 * @param colors
	 */
	protected void setColors(float[] colors) {
		// float has 4 bytes.
		// ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
		// cbb.order(ByteOrder.nativeOrder());
		// mColorBuffer = cbb.asFloatBuffer();
		// mColorBuffer.put(colors);
		// mColorBuffer.position(0);
		mColorBuffer = colors;
	}

	/**
	 * Loads the texture.
	 * 
	 * @param gl
	 */
	public void loadGLTexture() { // New function
		texture = TextureHelper.loadTexture(context, uri);
		// Log.d(ApplicationConstants.PACKAGE, "@@@@@@@@@@@@ texture.originalWidth=" + texture.originalWidth + "     texture.originalHeight=" + texture.originalHeight);
		// float[] v = {
		// // X, Y
		// 0, 0, 0.0f, 0, (texture.originalHeight - 1) , 0.0f, (texture.originalWidth - 1) , (texture.originalHeight - 1) , 0.0f,
		// (texture.originalWidth - 1) , 0, 0.0f };
		float[] v = { -texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 0, Top Left
				-texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 1, Bottom Left
				texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 2, Bottom Right
				-texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 0, Top Left
				texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 2, Bottom Right
				texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 3, Top Right
		};
		float[] t = { 0.0f, 0.0f, // 0,top left
				0.0f, (float) texture.originalHeight / texture.height, // 1,bottom left
				(float) texture.originalWidth / texture.width, (float) texture.originalHeight / texture.height, // 2,bottom right
				0.0f, 0.0f, // 0,top left
				(float) texture.originalWidth / texture.width, (float) texture.originalHeight / texture.height, // 2,bottom right
				(float) texture.originalWidth / texture.width, 0.0f, // 3,top right
		};
		width = texture.originalWidth;
		height = texture.originalHeight;
		setTextureCoordinates(t);
		setVertices(v);
	}

	public void setUri(String uri) {
		texture = null;
		this.uri = uri;
	}

	@Override
	public void setParent(IObject2D parent) {
		this.parent = parent;
	}

	@Override
	public IObject2D getParent() {
		return parent;
	}

	@Override
	public int getX() {
		return x;
	}

	@Override
	public void setX(int x) {
		this.x = x;
	}

	@Override
	public int getY() {
		return y;
	}

	@Override
	public void setY(int y) {
		this.y = y;
	}

	@Override
	public boolean isEnable() {
		return enable;
	}

	@Override
	public float getScale() {
		return scale;
	}

	@Override
	public void setScale(float scale) {
		this.scale = scale;
	}

	@Override
	public boolean contains(int ptx, int pty) {
		return contains(ptx, pty, 0);
	}

	@Override
	public boolean contains(int ptx, int pty, int tolerance) {
		int px = ptx - x;
		int py = pty - y;
		if (rotationDegree != 0) {
			double rotation = Math.PI * rotationDegree / 180;
			int npx = (int) (px * Math.cos(rotation) - py * Math.sin(rotation));
			int npy = (int) (px * Math.sin(rotation) + py * Math.cos(rotation));
			px = npx;
			py = npy;
		}
		if (px + tolerance < -texture.originalWidth / 2) {
			return false;
		}
		if (px - tolerance > +texture.originalWidth / 2) {
			return false;
		}
		if (py + tolerance < -texture.originalHeight / 2) {
			return false;
		}
		if (py - tolerance > +texture.originalHeight / 2) {
			return false;
		}
		return true;
	}

	@Override
	public void setGlobalScale(float globalScale) {
		this.scale = globalScale;
	}

	@Override
	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	@Override
	public boolean isVisible() {
		return visible;
	}

	@Override
	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	@Override
	public void removeObject(IObject2D obj) {
		// TODO Auto-generated method stub
	}
}
